#ifndef MONGOIO_H
#define MONGOIO_H

#include <bsoncxx/builder/stream/document.hpp>
#include <bsoncxx/builder/stream/array.hpp>
#include <bsoncxx/builder/concatenate.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/pool.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/uri.hpp>
#include <mongocxx/collection.hpp>
#include <bsoncxx/json.hpp>

namespace ORB_SLAM2 {

using bsoncxx::builder::stream::open_document;
using bsoncxx::builder::stream::close_document;
using bsoncxx::builder::stream::open_array;
using bsoncxx::builder::stream::close_array;
using bsoncxx::builder::stream::finalize;
using bsoncxx::builder::stream::document;

namespace MongoIO {

class ID {
public:

    ID() :
        status_(false)
    {}

    ID(const bsoncxx::types::b_oid & oid) :
        status_(true), oid_(oid)
    {}

    inline bsoncxx::types::b_oid oid() const {
        return oid_;
    }

    inline std::vector<char> value() const {
        return std::vector<char>(oid_.value.bytes(), oid_.value.bytes() + 12);
    }

    inline bool status() const {return status_;}


private:
    bool status_;
    bsoncxx::types::b_oid oid_;
};

class IO {
public:

    typedef std::shared_ptr<IO> Ptr;

    IO(const std::string uri) : pool_(mongocxx::uri{uri}) {}

    inline bsoncxx::types::b_oid WriteToDataBase(const std::string &database,
                                                 const std::string &collection,
                                                 const bsoncxx::document::view &doc)
    {
        auto client = pool_.acquire();
        const mongocxx::database db{(*client)[database]};
        mongocxx::collection coll(db[collection]);

        bsoncxx::stdx::optional<mongocxx::result::insert_one> result = coll.insert_one(doc);

        if (!result || result->inserted_id().type() != bsoncxx::type::k_oid) {
            std::cout << "Unacknowledged write. No id available." << std::endl;
            return bsoncxx::types::b_oid{};
        }

        return result->inserted_id().get_oid();
    }

    inline bool UpdateToDataBase(const std::string &database,
                                 const std::string &collection,
                                 const bsoncxx::types::b_oid &oid,
                                 const bsoncxx::document::view &update)
    {
        auto client = pool_.acquire();
        const mongocxx::database db{(*client)[database]};
        mongocxx::collection coll(db[collection]);

        bsoncxx::builder::stream::document fliter_builder{};
        bsoncxx::builder::stream::document update_builder{};
        fliter_builder << "_id" << oid;
        update_builder << "$set" << update;
        coll.update_one(fliter_builder.view(), update_builder.view());
        return true;
    }

    inline bool EraseFromDataBase(const std::string &database,
                                  const std::string &collection,
                                  const bsoncxx::types::b_oid &oid)
    {
        auto client = pool_.acquire();
        const mongocxx::database db{(*client)[database]};
        mongocxx::collection coll(db[collection]);

        bsoncxx::builder::stream::document fliter_builder{};
        fliter_builder << "_id" << oid;
        coll.delete_one(fliter_builder.view());
        return true;
    }

    inline bool FindFromDataBase(const std::string &database,
                                 const std::string &collection,
                                 const bsoncxx::types::b_oid &oid,
                                 const mongocxx::options::find &options,
                                 bsoncxx::stdx::optional<bsoncxx::document::value> &result)
    {
        auto client = pool_.acquire();
        const mongocxx::database db{(*client)[database]};
        mongocxx::collection coll{db[collection]};

        bsoncxx::builder::stream::document fliter_builder{};
        fliter_builder << "_id" << oid;
        result = coll.find_one(fliter_builder.view(), options);


        //std::cout << "find: " << bsoncxx::to_json(result.value().view()) << std::endl;
        return true;
    }

    inline mongocxx::cursor FindFromDatabase(const std::string &database, const std::string &collection)
    {
        auto client = pool_.acquire();
        const mongocxx::database db{(*client)[database]};
        mongocxx::collection coll{db[collection]};

        mongocxx::cursor cursor = coll.find(document{} << finalize);

        return cursor;
    }

    inline bool DropDatabase(const std::string &database)
    {
        auto client = pool_.acquire();
        mongocxx::database db((*client)[database]);
        db.drop();

        return true;
    }

    //! Buyi convert string to oid
    static inline MongoIO::ID StringtoID(const std::string _str)
    {
        bsoncxx::types::b_oid tb_oid;
        tb_oid.value = bsoncxx::oid(_str);
        MongoIO::ID _id(tb_oid);

        return _id;
    }

    static inline bsoncxx::types::b_oid Stringtoid(const std::string _str)
    {
        bsoncxx::types::b_oid tb_oid;
        tb_oid.value = bsoncxx::oid(_str);

        return tb_oid;
    }

private:
    mongocxx::pool pool_;

};



}//! namespace MomgoIO

}//! namespace ORB_SLAM2


#endif //MONGOIO_H
